
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h> /* mmap */

#include "tme_mmap.h"
#include "tme_log.h"

/* ---------- 本地内存记录 ---------- */

typedef struct TME_MmapUnit {
    int32_t quote; /* 引用次数,归0时释放 */
    int32_t offset;
    int32_t memSize; /* 内存大小,一般为 TME_MMAP_PAGE_SIZE */
    uint8_t* mem; /* 指针 */
    FILE* fp;
    struct TME_MmapUnit* prev;
    struct TME_MmapUnit* next;
} TME_MmapUnit;

typedef struct TME_MmapManage {
    TME_MmapUnit* unit;
    int32_t unitCount;
    uint32_t totalMemSize;
} TME_MmapManage;

static TME_MmapManage gMmapManage = {0};

static void _tme_mmap_release(uint8_t* mem, int32_t memSize)
{
    if (mem) {
        munmap((void*)mem, memSize);
    }
}

/* 加,注意这里 mem 要整数于 TME_MMAP_PAGE_SIZE */
static void _tme_mmap_add(FILE* fp, uint8_t* mem, int32_t memSize, int32_t offset)
{
    TME_MmapUnit* unit = gMmapManage.unit;
    /* 遍历现有链表 */
    while (unit)
    {
        /* 匹配已有,增加引用 */
        if (unit->fp == fp &&
            unit->mem == mem &&
            unit->memSize == memSize)
        {
            unit->quote += 1;
            return;
        }
        /* 下一个 */
        if (unit->next) {
            unit = unit->next;
        }
        else {
            break;
        }
    }
    /* 新加 */
    if (!unit) {
        unit = gMmapManage.unit = (TME_MmapUnit*)calloc(1, sizeof(TME_MmapUnit));
    }
    else {
        unit->next = (TME_MmapUnit*)calloc(1, sizeof(TME_MmapUnit));
        unit->next->prev = unit;
        unit = unit->next;
    }
    gMmapManage.unitCount += 1;
    gMmapManage.totalMemSize += memSize;
    /* 初始化 */
    unit->quote = 1;
    unit->fp = fp;
    unit->offset = offset;
    unit->mem = mem;
    unit->memSize = memSize;
    TME_DEBUG("new mmap: fp:%p mem:%p memSize:%d offset:%d(0x%X)", fp, mem, memSize, offset, offset);
}

/* 减,这里 mem 不要求整数于 TME_MMAP_PAGE_SIZE */
static void _tme_mmap_remove(uint8_t* mem)
{
    TME_MmapUnit* unit = gMmapManage.unit;
    /* 遍历现有链表 */
    while (unit)
    {
        /* 匹配已有,检查在范围内 */
        if (mem >= unit->mem &&
            mem < unit->mem + unit->memSize)
        {
            /* 减引用次数 */
            unit->quote -= 1;
            /* 引用为0,真实释放 */
            if (unit->quote == 0)
            {
                gMmapManage.unitCount -= 1;
                gMmapManage.totalMemSize -= unit->memSize;
                /* 解除链表关系 */
                TME_MmapUnit* prev = unit->prev;
                TME_MmapUnit* next = unit->next;
                if (next) {
                    next->prev = prev;
                }
                if (prev) {
                    prev->next = next;
                }
                else {
                    gMmapManage.unit = next;
                }
                TME_DEBUG("del mmap: fp:%p mem:%p memSize:%d offset:%d(0x%X) %s",
                    unit->fp, unit->mem, unit->memSize, unit->offset, unit->offset,
                    gMmapManage.unitCount ? "" : "(clear)");
                /* 释放 */
                _tme_mmap_release(unit->mem, unit->memSize);
                free(unit);
            }
            return;
        }
        /* 下一个 */
        if (unit->next) {
            unit = unit->next;
        }
        else {
            break;
        }
    }
    /* 漏网之鱼? */
    TME_ERR("unknown mem:%p, did not unmap !!", mem);
}

/* 从现有缓存直接取得地址,成功返回0 */
static int32_t _tme_mmap_search(FILE* fp, uint8_t** mem, int32_t memSize, int32_t offset)
{
    TME_MmapUnit* unit = gMmapManage.unit;
    /* 遍历现有链表 */
    while (unit)
    {
        /* 匹配已有,检查在范围内 */
        if (unit->fp == fp &&
            offset >= unit->offset &&
            offset + memSize <= unit->offset + unit->memSize)
        {
            /* 增加引用,直接返回 */
            unit->quote += 1;
            *mem = unit->mem + (offset - unit->offset);
            return 0;
        }
        /* 下一个 */
        if (unit->next) {
            unit = unit->next;
        }
        else {
            break;
        }
    }
    return -1;
}

/* 参数调整至 TME_MMAP_PAGE_SIZE 整数倍 */
static void _tme_mmap_paramAdapt(
    int32_t memSize, int32_t* retMemSize,
    int32_t offset, int32_t* retOffset)
{
    int32_t multi = offset / TME_MMAP_PAGE_SIZE;
    int32_t remain = offset % TME_MMAP_PAGE_SIZE;
    /* 跨区内存,分配2+倍 TME_MMAP_PAGE_SIZE 来覆盖 */
    if (remain + memSize > TME_MMAP_PAGE_SIZE) {
        *retMemSize = (remain + memSize) + (TME_MMAP_PAGE_SIZE - ((remain + memSize) % TME_MMAP_PAGE_SIZE));
    }
    else {
        *retMemSize = TME_MMAP_PAGE_SIZE;
    }
    *retOffset = multi * TME_MMAP_PAGE_SIZE;
}

/* ---------- api ---------- */

void* tme_mmap_create(FILE* fp, int32_t _memSize, int32_t _offset, int32_t isReadOnly)
{
    void* _mem = NULL;
    do
    {
        if (_memSize < 1) {
            TME_ERR("memSize wrong: fp:%p memSize:%d offset:%d(0x%X)", fp, _memSize, _offset, _offset);
            break;
        }

        /* 现有匹配 */
        if (_tme_mmap_search(fp, (uint8_t**)&_mem, _memSize, _offset) == 0) {
            break;
        }

        /* 调整为 TME_MMAP_PAGE_SIZE 整数倍 */
        int32_t memSize = _memSize;
        int32_t offset = _offset;
        _tme_mmap_paramAdapt(_memSize, &memSize, _offset, &offset);

        /* 计算文件大小 */
        fseek(fp, 0, SEEK_END);
        int32_t fileSize = ftell(fp);

        /* 拓宽文件 */
        if (fileSize < offset + memSize)
        {
            if (isReadOnly) {
                TME_ERR("readOnly mode out of range: fp:%p fileSize:%d < memSize:%d + offset:%d(0x%X)", fp, fileSize, memSize, offset, offset);
                break;
            }
            int32_t tmpSize = offset + memSize - fileSize;
            uint8_t* tmp = (uint8_t*)calloc(1, tmpSize);
            if (!tmp || fwrite(tmp, 1, tmpSize, fp) != tmpSize)
            {
                if (tmp) {
                    free(tmp);
                }
                TME_ERR("fwrite tmp:%p size:%d failed: fp:%p memSize:%d offset:%d(0x%X)", tmp, tmpSize, fp, _memSize, _offset, _offset);
                break;
            }
            free(tmp);
            fflush(fp);
        }

        /* 内存映射 */
        void* pMem = mmap(0, memSize, isReadOnly ? PROT_READ : (PROT_READ | PROT_WRITE), MAP_SHARED, fileno(fp), offset);
        if (pMem == NULL || pMem == (void*)-1) {
            TME_ERR("mmap failed: fp:%p memSize:%d offset:%d(0x%X)", fp, memSize, offset, offset);
            break;
        }

        /* 记录 */
        _tme_mmap_add(fp, (uint8_t*)pMem, memSize, offset);

        /* 矫正位置并获得指针 */
        _mem = (void*)(((uint8_t*)pMem) + (_offset - offset));
    } while(0);
    return _mem;
}

void tme_mmap_release(void* mem)
{
    if (!mem) {
        return;
    }
    _tme_mmap_remove((uint8_t*)mem);
}

int32_t tme_mmap_getTotalMemSize()
{
    return gMmapManage.totalMemSize;
}

int32_t tme_mmap_getTotalMemCount()
{
    return gMmapManage.unitCount;
}
